﻿using EarthGrid.core;
using EarthGrid.sqlite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EarthGrid.app
{
    public class GridToSqlite
    {
        public string FilePath { get; set; }
        public LonLat MinLon { get; set; } = new LonLat(LonLatEnum.lon, -256, 0, 0);
        public LonLat MinLat { get; set; } = new LonLat(LonLatEnum.lat, -256, 0, 0);
        public LonLat MaxLon { get; set; } = new LonLat(LonLatEnum.lon, 256, 0, 0);
        public LonLat MaxLat { get; set; } = new LonLat(LonLatEnum.lat, 256, 0, 0);
        public int MinLevel { get; set; } = 1;
        public int MaxLevel { get; set; } = 32;
        public int ThreadsCount { get; set; } = 16;
        public SqliteTool sqliteTool { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="minLon"></param>
        /// <param name="minLat"></param>
        /// <param name="maxLon"></param>
        /// <param name="maxLat"></param>
        /// <param name="minLevel"></param>
        /// <param name="maxLevel"></param>
        public async ValueTask CreateGrid(LonLat minLon = null, LonLat minLat = null, LonLat maxLon = null, LonLat maxLat = null, int minLevel = 1, int maxLevel = 32)
        {
            if (null == minLon) minLon = MinLon;
            else MinLon = minLon;
            if (null == minLat) minLat = MinLat;
            else MinLat = minLat;
            if (null == maxLon) maxLon = MaxLon;
            else MaxLon = maxLon;
            if (null == maxLat) maxLat = MaxLat;
            else MaxLat = maxLat;
            MinLevel = minLevel;
            MaxLevel = maxLevel;
            if (null != sqliteTool) sqliteTool.BeginTransaction();
            if (maxLevel < 8)
            {
                Grid g = 获取父级网格(minLon, minLat, maxLon, maxLat);
                if (g.Level == 0)
                {
                    分8级以内(new Grid[4] {Grid.FirstLevelGridG0, Grid.FirstLevelGridG1, Grid.FirstLevelGridG2, Grid.FirstLevelGridG3 });
                }
                else
                {
                    saveGrid(g);
                    if (g.Level >= maxLevel) return;
                    Grid[] gs = GridTool.createGridFromParent(g, false);
                    分8级以内(gs);
                }
            }
            else 
            {
                if (maxLat.to十进制() > 88)
                {
                    // 从88度分开，两份范围
                    LonLat lat88 = new LonLat(LonLatEnum.lat, 88, 0, 0);
                    南北极分割(true);
                    maxLat = lat88;
                }
                if (minLat.to十进制() < -88)
                {
                    // 从-88度分开，两份范围
                    LonLat lat88 = new LonLat(LonLatEnum.lat, -88, 0, 0);
                    南北极分割(false);
                    minLat = lat88;
                }
                Grid g = 获取父级网格(minLon, minLat, maxLon, maxLat);
                if (g.Level == 0)
                {
                    if (1 == minLevel) saveGrid(new Grid[4] {Grid.FirstLevelGridG0, Grid.FirstLevelGridG1, Grid.FirstLevelGridG2, Grid.FirstLevelGridG3 });
                    await 多线程分Async(Grid.FirstLevelGridG0);
                    await 多线程分Async(Grid.FirstLevelGridG1);
                    await 多线程分Async(Grid.FirstLevelGridG2);
                    await 多线程分Async(Grid.FirstLevelGridG3);
                } else
                {
                    saveGrid(g);
                    // 使用多线程分
                    await 多线程分Async(g);
                }
                
            }
            if (null != sqliteTool) sqliteTool.TransactionCommit();
        }
        /// <summary>
        /// 判断给出的范围，计算起始位置，合适的起始级别，起始网格的编码,不支持跨正负180°经度线的范围
        /// </summary>
        /// <param name="minLon"></param>
        /// <param name="minLat"></param>
        /// <param name="maxLon"></param>
        /// <param name="maxLat"></param>
        /// <returns></returns>
        public Grid 获取父级网格(LonLat minLon, LonLat minLat, LonLat maxLon, LonLat maxLat)
        {
            double len_lon = Math.Abs(maxLon.to十进制() - minLon.to十进制());
            double len_lat = Math.Abs(maxLat.to十进制() - minLat.to十进制());
            double len = len_lon > len_lat ? len_lon : len_lat;

            (int du, int fen, double miao) = GridTool.十进制转度分秒(len);
            // 计算分割合适级别
            int minLevel = 1;
            if (du > 0)
            {
                while (512 >> (minLevel + 1) > du)
                {
                    minLevel++;
                }
            }
            else if (fen > 0)
            {
                minLevel = 0;
                while (64 >> (minLevel + 1) > fen)
                {
                    minLevel++;
                }
                minLevel += 9;
            }
            else if (miao > 1)
            {
                minLevel = 0;
                while (64 >> (minLevel + 1) > miao)
                {
                    minLevel++;
                }
                minLevel += 15;
            }
            else
            {
                minLevel = 0;
                while(1 / Math.Pow(2, minLevel + 1) > miao - (int)miao)
                {
                    minLevel++;
                }
                minLevel += 21;
            }
            if (1 == minLevel)
            {
                Grid G = new Grid();
                G.Code = "G";
                G.Level = 0;
                G.SetBox(new LonLat(LonLatEnum.lon, -256, 0, 0), new LonLat(LonLatEnum.lat, -256,0,0), new LonLat(LonLatEnum.lon, 256,0,0),new LonLat(LonLatEnum.lat, 256,0,0));
                return G;
            }

            // 取左下点和右上点网格编号
            string code_ld = GridTool.C_获取经纬度所在球面网格编码(minLon, minLat);
            string code_ru = GridTool.C_获取经纬度所在球面网格编码(maxLon, maxLat);
            // Console.WriteLine("zuo xia dian : {0}, you shang dian : {1}", code_ld, code_ru);
            // Console.WriteLine("级别{0}左下网格是：{1}", minLevel, code_ld.Substring(0,minLevel+1));

            // 取两个编号前面相同部分
            int j = minLevel;
            while(!code_ld.Substring(0,j + 1).Equals(code_ru.Substring(0, j + 1)))
            {
                j--;
            }
            string code_parent = code_ru.Substring(0, j + 1);
            Console.WriteLine("两个的上级编码 {0}", code_ru.Substring(0, j + 1));

            Point point_1 = GridTool.P_根据球面网格编码获取定位点(code_parent);
            Console.WriteLine("起始经度 "+point_1.Lon.ToString() + " , 纬度 " + point_1.Lat.ToString());
            (LonLat g_minLon, LonLat g_minLat, LonLat g_maxLon, LonLat g_maxLat) = get_网格四至范围(point_1, minLevel);

            Grid g = new Grid();
            g.Code = code_parent;
            g.SetBox(g_minLon, g_minLat, g_maxLon, g_maxLat);
            g.Level = minLevel;
            return g;
        }

        private void 分8级以内(Grid[] gs)
        {
            if (null == gs) return;
            
            for (int i = 0; i < gs.Length; i++)
            {
                Grid g = gs[i];
                if (!g.是否有效())
                {
                    continue;
                }

                if (!IsIn(g)) continue;

                if (g.Level >= MinLevel)
                {
                    saveGrid(g);
                }

                if (g.Level >= MaxLevel)
                {
                    return;
                }
                if (g.Level < 8)
                    分8级以内(GridTool.createGridFromParent(g, false));
            }
        }

        private async ValueTask 多线程分Async(Grid grid)
        {
            Grid[] gs = GridTool.createGridFromParent(grid, false);
            if (null == gs) return;
            List<Grid> list = new List<Grid>();
            for (int i = 0; i < gs.Length; i++)
            {
                if (!gs[i].是否有效()) continue;
                //list.Add(gs[i]);
                if (IsIn(gs[i])){
                    if (gs[i].Level >= MinLevel)
                        saveGrid(gs[i]);
                    if (gs[i].Level == MaxLevel) continue;

                    Grid[] gss = GridTool.createGridFromParent(gs[i], false);
                    if (null == gss) continue;
                    for (int j = 0; j < gss.Length; j++)
                    {
                        if (!gss[j].是否有效()) continue;
                        if (IsIn(gss[j]))
                        {
                            if (gss[j].Level >= MinLevel)
                                saveGrid(gss[j]);
                            list.Add(gss[j]);
                        }
                    }
                }
            }
            using (SemaphoreSlim semaphoreSlim = new SemaphoreSlim(ThreadsCount))
            {
                List<Task> tasks = new List<Task>();
                await semaphoreSlim.WaitAsync();

                foreach (Grid g in list)
                {
                    if (g.MinLon.to十进制() >= MinLon.to十进制() && g.MaxLon.to十进制() <= MaxLon.to十进制() &&
                        g.MinLat.to十进制() >= MinLat.to十进制() && g.MaxLat.to十进制() <= MaxLat.to十进制())
                    {
                        tasks.Add(Task.Run(() => 往下分(GridTool.createGridFromParent(g, false), false)));
                    }
                    else
                    {
                        tasks.Add(Task.Run(() => 往下分(GridTool.createGridFromParent(g, false), true)));
                    }
                }

                await Task.WhenAll(tasks);
                foreach (Task task in tasks) task.Dispose();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="gs"></param>
        /// <param name="check">是否对网格进行有效性和越界检查</param>
        private void 往下分(Grid[] gs, bool check)
        {
            if (null == gs) return;
            if (check)
            {
                for (int i = 0; i < gs.Length; i++)
                {
                    if (gs[i].是否有效())
                    {
                        if (IsIn(gs[i]))
                        {
                            if (gs[i].Level >= MinLevel)
                                saveGrid(gs[i]);

                            if (gs[i].Level < MaxLevel)
                            {
                                if (gs[i].Level == 8 && (gs[i].MinLat.Du >= 88 || gs[i].MaxLat.Du <= -88)) continue;

                                if (gs[i].MinLon.to十进制() >= MinLon.to十进制() && gs[i].MaxLon.to十进制() <= MaxLon.to十进制() &&
                                    gs[i].MinLat.to十进制() >= MinLat.to十进制() && gs[i].MaxLat.to十进制() <= MaxLat.to十进制())
                                {
                                    往下分(GridTool.createGridFromParent(gs[i], false), false);
                                }
                                else
                                {
                                    往下分(GridTool.createGridFromParent(gs[i], false), true);
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                for (int i = 0; i < gs.Length; i++)
                {
                    if (gs[i].Level == 8 && (gs[i].MinLat.Du >= 88 || gs[i].MaxLat.Du <= -88)) continue;
                    if (gs[i].是否有效())
                    {
                        if (gs[i].Level >= MinLevel)
                            saveGrid(gs[i]);
                        if (gs[i].Level < MaxLevel)
                            往下分(GridTool.createGridFromParent(gs[i], false), false);
                    }
                }
            }
        }

        private void 南北极分割(bool n)
        {
            Grid[] gs_8 = GridTool.CreateLevel8GridLat88to90();
            if (n)
            {
                if (gs_8[0].Level >= MinLevel) saveGrid(gs_8[0]);
                P0分割(GridTool.南北极四分法(gs_8[0]));
            }
            else
            {
                if (gs_8[1].Level >= MinLevel) saveGrid(gs_8[1]);
                P0分割(GridTool.南北极四分法(gs_8[1]));
            }
        }

        private void P0分割(Grid[] gs)
        {
            if (gs[0].Level >= MinLevel && gs[0].Level <= MaxLevel)
            {
                if (IsIn(gs[0])) 
                { 
                    saveGrid(gs[0]); 
                    P0分割(GridTool.南北极四分法(gs[0])); 
                }
                if (IsIn(gs[2]))
                {
                    saveGrid(gs[2]);
                    P外层分割(GridTool.南北极P2跨正负的四分法(gs[2]));
                }
                if (IsIn(gs[1])) 
                {
                    saveGrid(gs[1]);
                    P外层分割(GridTool.南北极P1P3的四分法(gs[1]));
                }
                if (IsIn(gs[3]))
                {
                    saveGrid(gs[3]);
                    P外层分割(GridTool.南北极P1P3的四分法(gs[3]));
                }
            }
            else if (gs[0].Level < MinLevel)
            {
                if (IsIn(gs[0]))
                {
                    P0分割(GridTool.南北极四分法(gs[0]));
                }
                if (IsIn(gs[2]))
                {
                    P外层分割(GridTool.南北极P2跨正负的四分法(gs[2]));
                }
                if (IsIn(gs[1]))
                {
                    P外层分割(GridTool.南北极P1P3的四分法(gs[1]));
                }
                if (IsIn(gs[3]))
                {
                    P外层分割(GridTool.南北极P1P3的四分法(gs[3]));
                }
            }
        }

        private void P外层分割(Grid[] gs)
        {
            if (gs[0].Level >= MinLevel) saveGrid(gs);
            if (gs[0].Level >= MaxLevel) return;
            for (int i = 0; i < gs.Length; i++)
            {
                if (gs[i].是否有效())
                    P外层分割(GridTool.南北极10级以下外层分法(gs[i]));
            }
        }

        private bool IsIn(Grid g)
        {
            if (g.MinLon.Du > MaxLon.Du) return false;
            if (g.MaxLon.Du < MinLon.Du) return false;
            if (g.MinLat.Du > MaxLat.Du) return false;
            if (g.MaxLat.Du < MinLat.Du) return false;
            return true;
        }

        /// <summary>
        /// 获取网格四至范围
        /// </summary>
        /// <param name="p">起始坐标点</param>
        /// <param name="level">分割级别</param>
        /// <returns></returns>
        private (LonLat minLon, LonLat minLat, LonLat maxLon, LonLat maxLat) get_网格四至范围(Point p, int level)
        {
            LonLat minLon = null, minLat = null, maxLon = null, maxLat = null;
            switch (p.Hemisphere())
            {
                case Hemisphere.G0:
                    minLon = p.Lon;
                    minLat = p.Lat;
                    maxLon = minLon.copy();
                    maxLat = minLat.copy();
                    if (level <= 9)
                    {
                        maxLon.Du += 512 >> level;
                        maxLat.Du += 512 >> level;
                    } else if (level <= 15)
                    {
                        maxLon.Fen += 64 >> (level - 9);
                        maxLat.Fen += 64 >> (level - 9);
                    } else if (level <= 21)
                    {
                        maxLon.Miao += 64 >> (level - 15);
                        maxLat.Miao += 64 >> (level - 15);
                    } else
                    {
                        maxLon.Miao += 1 / Math.Pow(2, level - 21);
                        maxLat.Miao += 1 / Math.Pow(2, level - 21);
                    }
                    break;
                case Hemisphere.G1:
                    maxLon = p.Lon;
                    minLat = p.Lat;
                    minLon = maxLon.copy();
                    maxLat = minLat.copy();
                    if (level <= 9)
                    {
                        minLon.Du -= 512 >> level;
                        maxLat.Du += 512 >> level;
                    } else if (level <= 15)
                    {
                        minLon.Fen -= 64 >> (level - 9);
                        maxLat.Fen += 64 >> (level - 9);
                    } else if (level <= 21)
                    {
                        minLon.Miao -= 64 >> (level - 15);
                        maxLat.Miao += 64 >> (level - 15);
                    } else
                    {
                        minLon.Miao -= 1 / Math.Pow(2, level - 21);
                        maxLat.Miao += 1 / Math.Pow(2, level - 21);
                    }
                    break;
                case Hemisphere.G2:
                    minLon = p.Lon;
                    maxLat = p.Lat;
                    maxLon = minLon.copy();
                    minLat = maxLat.copy();
                    if (level <= 9)
                    {
                        minLat.Du -= 512 >> level;
                        maxLon.Du += 512 >> level;
                    }
                    else if (level <= 15)
                    {
                        minLat.Fen -= 64 >> (level - 9);
                        maxLon.Fen += 64 >> (level - 9);
                    }
                    else if (level <= 21)
                    {
                        minLat.Miao -= 64 >> (level - 15);
                        maxLon.Miao += 64 >> (level - 15);
                    }
                    else
                    {
                        minLat.Miao -= 1 / Math.Pow(2, level - 21);
                        maxLon.Miao += 1 / Math.Pow(2, level - 21);
                    }
                    break;
                case Hemisphere.G3:
                    maxLon = p.Lon;
                    maxLat = p.Lat;
                    minLon = maxLon.copy();
                    minLat = maxLat.copy();
                    if (level <= 9)
                    {
                        minLat.Du -= 512 >> level;
                        minLon.Du -= 512 >> level;
                    }
                    else if (level <= 15)
                    {
                        minLat.Fen -= 64 >> (level - 9);
                        minLon.Fen -= 64 >> (level - 9);
                    }
                    else if (level <= 21)
                    {
                        minLat.Miao -= 64 >> (level - 15);
                        minLon.Miao -= 64 >> (level - 15);
                    }
                    else
                    {
                        minLat.Miao -= 1 / Math.Pow(2, level - 21);
                        minLon.Miao -= 1 / Math.Pow(2, level - 21);
                    }
                    break;
            }
            return (minLon, minLat, maxLon, maxLat);
        }

        private void saveGrid(Grid g)
        {
            sqliteTool.SaveGrid(g.Level, g.Code, g.MinLon.to十进制(), g.MinLat.to十进制(), g.MaxLon.to十进制(), g.MaxLat.to十进制());
        }

        private void saveGrid(Grid[] gs)
        {
            for (int i = 0; i < gs.Length; i++)
            {
                sqliteTool.SaveGrid(gs[i].Level, gs[i].Code, gs[i].MinLon.to十进制(), gs[i].MinLat.to十进制(), gs[i].MaxLon.to十进制(), gs[i].MaxLat.to十进制());
            }
        }
        
    }
}
